{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial 0: Getting Started\n",
    "\n",
    "Welcome to TenSEAL's first tutorial of a series aiming at introducing homomorphic encryption and the capabilities of the library.\n",
    "\n",
    "TenSEAL is a library for doing homomorphic encryption operations on tensors. It's built on top of [Microsoft SEAL](https://github.com/Microsoft/SEAL), a C++ library implementing the BFV and CKKS homomorphic encryption schemes. TenSEAL provides ease of use through a Python API, while preserving efficiency by implementing most of its operations using C++, so TenSEAL is a C++ library with a Python interface. \n",
    "\n",
    "Let's now start the tutorial with a brief review of what homomorphic encryption is, but keep in mind that you don't need to be a crypto expert to use the library.\n",
    "\n",
    "\n",
    "Authors:\n",
    "- Ayoub Benaissa - Twitter: [@y0uben11](https://twitter.com/y0uben11)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Homomorphic Encryption\n",
    "\n",
    "__Definition__ : Homomorphic encryption (HE) is an encryption technique that allows computations to be made on ciphertexts and generates results that when decrypted, correspond to the results of the same computations made on plaintexts.\n",
    "\n",
    "<img src=\"assets/he-black-box.png\" alt=\"he-black-box\" width=\"600\"/>\n",
    "\n",
    "This means that an HE scheme lets you encrypt two numbers *X* and *Y*, add their encrypted versions so that the result gets decrypted to *X + Y*. The same works for multiplication. If we translate this to Python, it may look something like this:\n",
    "\n",
    "```python\n",
    "x = 7\n",
    "y = 3\n",
    "\n",
    "x_encrypted = HE.encrypt(x)\n",
    "y_encrypted = HE.encrypt(y)\n",
    "\n",
    "z_encrypted = x_encrypted + y_encrypted\n",
    "\n",
    "# z should now be x + y = 10\n",
    "z = HE.decrypt(z_encrypted)\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "Many details are hidden in this Python script, things like key generation doesn't appear, and that `+` operation over encrypted numbers isn't the usual `+` over integers, but a special evaluation algorithm that can evaluate addition over encrypted numbers. TenSEAL supports addition, subtraction and multiplication of encrypted vectors of either integers (using BFV) or real numbers (using CKKS).\n",
    "\n",
    "Next we will look at the most important object of the library, the TenSEALContext."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TenSEALContext\n",
    "\n",
    "The TenSEALContext is a special object that holds different encryption keys and parameters for you, so that you only need to use a single object to make your encrypted computation instead of managing all the keys and the HE details. Basically, you will want to create a single TenSEALContext before doing your encrypted computation. Let's see how to create one !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<_tenseal_cpp.TenSEALContext at 0x7fcb980c71f0>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import tenseal as ts\n",
    "\n",
    "context = ts.context(ts.SCHEME_TYPE.BFV, poly_modulus_degree=4096, plain_modulus=1032193)\n",
    "context"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That's it ! We need to specify the HE scheme (BFV here) that we want to use, as well as its parameters. Don't worry about the parameters now, you will learn more about them in upcoming tutorials.\n",
    "\n",
    "An important thing to note is that the TenSEALContext is now holding the secret key and you can decrypt without the need to provide it, however, you can choose to manage it as a separate object and you will need to pass it to functions that require the secret key. Let's see how this translates into Python!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Is the context private? Yes\n",
      "Is the context public? No\n",
      "Secret-key dropped\n",
      "Is the context private? No\n",
      "Is the context public? Yes\n"
     ]
    }
   ],
   "source": [
    "public_context = ts.context(ts.SCHEME_TYPE.BFV, poly_modulus_degree=4096, plain_modulus=1032193)\n",
    "print(\"Is the context private?\", (\"Yes\" if public_context.is_private() else \"No\"))\n",
    "print(\"Is the context public?\", (\"Yes\" if public_context.is_public() else \"No\"))\n",
    "\n",
    "sk = public_context.secret_key()\n",
    "\n",
    "# the context will drop the secret-key at this point\n",
    "public_context.make_context_public()\n",
    "print(\"Secret-key dropped\")\n",
    "print(\"Is the context private?\", (\"Yes\" if public_context.is_private() else \"No\"))\n",
    "print(\"Is the context public?\", (\"Yes\" if public_context.is_public() else \"No\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can now try to fetch the secret key from the `public_context` and see that it raises an error. We will now continue using our first created TenSEALContext `context` which is still holding the secret key."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Encryption and Evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next step after creating our TenSEALContext is to start doing some encrypted computation. First, we create an encrypted vector of integers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "We just encrypted our plaintext vector of size: 5\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<_tenseal_cpp.BFVVector at 0x7fcb980bc330>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plain_vector = [60, 66, 73, 81, 90]\n",
    "encrypted_vector = ts.bfv_vector(context, plain_vector)\n",
    "print(\"We just encrypted our plaintext vector of size:\", encrypted_vector.size())\n",
    "encrypted_vector"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we encrypted a vector of integers into a BFVVector, a vector type that uses the BFV scheme. Now we can do both addition, subtraction and multiplication in an element-wise fashion with other encrypted or plain vectors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[61, 68, 76, 85, 95]\n"
     ]
    }
   ],
   "source": [
    "add_result = encrypted_vector + [1, 2, 3, 4, 5]\n",
    "print(add_result.decrypt())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[59, 64, 70, 77, 85]\n"
     ]
    }
   ],
   "source": [
    "sub_result = encrypted_vector - [1, 2, 3, 4, 5]\n",
    "print(sub_result.decrypt())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[60, 132, 219, 324, 450]\n"
     ]
    }
   ],
   "source": [
    "mul_result = encrypted_vector * [1, 2, 3, 4, 5]\n",
    "print(mul_result.decrypt())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[120, 132, 146, 162, 180]\n"
     ]
    }
   ],
   "source": [
    "encrypted_add = add_result + sub_result\n",
    "print(encrypted_add.decrypt())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[60, 66, 73, 81, 90]\n"
     ]
    }
   ],
   "source": [
    "encrypted_sub = encrypted_add - encrypted_vector\n",
    "print(encrypted_sub.decrypt())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7200, 8712, 10658, 13122, 16200]\n"
     ]
    }
   ],
   "source": [
    "encrypted_mul = encrypted_add * encrypted_sub\n",
    "print(encrypted_mul.decrypt())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We just made both ciphertext to plaintext (c2p) and ciphertext to ciphertext (c2c) evaluations (add, sub and mul). An important thing to note is that you should never encrypt your plaintext values to evaluate them with ciphertexts if they don't need to be kept private. That's because c2p evaluations are more efficient than c2c. Look at the below script to see how much faster a c2p multiplication is compared to a c2c one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "c2c multiply time: 18.739938735961914 ms\n",
      "c2p multiply time: 1.5423297882080078 ms\n"
     ]
    }
   ],
   "source": [
    "from time import time\n",
    "\n",
    "t_start = time()\n",
    "_ = encrypted_add * encrypted_mul\n",
    "t_end = time()\n",
    "print(\"c2c multiply time: {} ms\".format((t_end - t_start) * 1000))\n",
    "\n",
    "t_start = time()\n",
    "_ = encrypted_add * [1, 2, 3, 4, 5]\n",
    "t_end = time()\n",
    "print(\"c2p multiply time: {} ms\".format((t_end - t_start) * 1000))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## More about TenSEALContext\n",
    "\n",
    "TenSEALContext is holding more attributes than what we have seen so far, so it's worth mentioning some other interesting ones. The coolest attributes (at least to me) are the ones for setting automatic relinearization, rescaling (for CKKS only) and modulus switching. These features are enabled by default as you can see below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Automatic relinearization is: on\n",
      "Automatic rescaling is: on\n",
      "Automatic modulus switching is: on\n"
     ]
    }
   ],
   "source": [
    "print(\"Automatic relinearization is:\", (\"on\" if context.auto_relin else \"off\"))\n",
    "print(\"Automatic rescaling is:\", (\"on\" if context.auto_rescale else \"off\"))\n",
    "print(\"Automatic modulus switching is:\", (\"on\" if context.auto_mod_switch else \"off\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Experienced users can choose to disable one or more of these features and manage for themselves when and how to do these operations.\n",
    "\n",
    "TenSEALContext can also hold a `global_scale` (only used when using CKKS), which is used as a default scale value when the user doesn't provide one. As most often users will define a single value to be used as scale during the entire HE computation, defining it globally can be more straight forward compared to passing it to every function call."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The global_scale isn't defined yet\n",
      "global_scale: 1048576.0\n"
     ]
    }
   ],
   "source": [
    "# this should throw an error as the global_scale isn't defined yet\n",
    "try:\n",
    "    print(\"global_scale:\", context.global_scale)\n",
    "except ValueError:\n",
    "    print(\"The global_scale isn't defined yet\")\n",
    "    \n",
    "# you can define it to 2 ** 20 for instance\n",
    "context.global_scale = 2 ** 20\n",
    "print(\"global_scale:\", context.global_scale)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Congratulations!!! - Time to Join the Community!\n",
    "\n",
    "Congratulations on completing this notebook tutorial! If you enjoyed this and would like to join the movement towards privacy preserving, decentralized ownership of AI and the AI supply chain (data), you can do so in the following ways!\n",
    "\n",
    "### Star TenSEAL on GitHub\n",
    "\n",
    "The easiest way to help our community is just by starring the repos! This helps raise awareness of the cool tools we're building.\n",
    "\n",
    "- [Star TenSEAL](https://github.com/OpenMined/TenSEAL)\n",
    "\n",
    "### Join our Slack!\n",
    "\n",
    "The best way to keep up to date on the latest advancements is to join our community! You can do so by filling out the form at [http://slack.openmined.org](http://slack.openmined.org).\n",
    "\n",
    "### Donate\n",
    "\n",
    "If you don't have time to contribute to our codebase, but would still like to lend support, you can also become a Backer on our Open Collective. All donations go towards our web hosting and other community expenses such as hackathons and meetups!\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
